﻿using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer.EsgRatings;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.ESG.Entities;
using Sustainalytics.ESGRatings.BusinessLogic.Models;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ESGRatings.BusinessLogic.ESGOnlineReport
{

    public class OnlineResearchProductsBusinessLogicUnit : IOnlineResearchProductsBusinessLogicUnit
    {
        private readonly IRepositoryCollection<RPCompanyESGComputationResult> _computationRepository;
        private readonly IControversyIndicatorsRepository _controversyIndicatorRepository;
        private readonly IRepositoryCollection<RPControversyIndicator> _controversyIndicatorsRepository;
        private readonly IRepositoryCollection<RPCompanyESGComputationResult> _esgComputationResultRepository;
        private readonly IStandardIndicatorsRepository _standardIndicatorsRepository;
        private readonly IRepositoryCollection<IndicatorTemplate> _templatesRepository;
        private readonly IMongoCollection<CorporateData> _corporateDataCollection;
        private readonly IMongoCollection<RPCompanyProfile> _rpCompanyProfileRepositoryCollection;

        public OnlineResearchProductsBusinessLogicUnit(string cnxString)
        {
            _esgComputationResultRepository = new RepositoryCollection<RPCompanyESGComputationResult>(cnxString);

            _templatesRepository = new RepositoryCollection<IndicatorTemplate>(cnxString);
            _standardIndicatorsRepository = new StandardIndicatorsRepository(cnxString);
            _controversyIndicatorRepository = new ControversyIndicatorsRepository(cnxString);

            _corporateDataCollection = MongoFactory.GetCollectionFromDatabase<CorporateData>(cnxString);
            _rpCompanyProfileRepositoryCollection = MongoFactory.GetCollectionFromDatabase<RPCompanyProfile>(cnxString);
        }

        public OnlineResearchProductsBusinessLogicUnit(IRepositoryCollection<IndicatorTemplate> templatesRepository)
        {
            _templatesRepository = templatesRepository;
        }

        public OnlineResearchProductsBusinessLogicUnit(IRepositoryCollection<IndicatorTemplate> templatesRepository
            , IStandardIndicatorsRepository standardIndicatorsRepository
            , IControversyIndicatorsRepository controversyIndicatorRepository
            , IRepositoryCollection<RPCompanyESGComputationResult> computationRepository)
        {
            _standardIndicatorsRepository = standardIndicatorsRepository;
            _controversyIndicatorRepository = controversyIndicatorRepository;
            _computationRepository = computationRepository;
            _templatesRepository = templatesRepository;
        }

        public OnlineResearchProductsBusinessLogicUnit(
            IRepositoryCollection<RPControversyIndicator> controversyIndicatorsRepository)
        {
            _controversyIndicatorsRepository = controversyIndicatorsRepository;
        }

        public IEnumerable<RPControversyIndicator> GetControversyIndicatorsByCompanyId(Guid companyId, IEnumerable<int> universeIds)
        {
            var collection =
                _controversyIndicatorsRepository.ReadAllWhere(x => x.CompanyId == companyId);

            if (collection.IsNullOrEmpty())
            {
                throw new ESGReasearchNotFoundException();
            }

            if (!universeIds.IsNullOrEmpty())
            {
                collection = collection.Where(x => universeIds.Intersect(x.UniverseIds).Any()).ToList();
                if (collection.IsNullOrEmpty())
                {
                    throw new ESGReasearchNotInUniversesException();
                }
            }
            return collection.OrderBy(x => x.Template.SortKey);
        }

        public EsgOverallPerformanceSummary GetEsgOverallPerformanceSummary(Guid companyId, Guid esgProfileId)
        {
            var result = _esgComputationResultRepository.ReadAllWhere(x => x.Id == companyId).FirstOrDefault();
            if (result == null)
                return null;

            var company = _corporateDataCollection
                .Find(x => x.Id == companyId)
                .Project<CorporateData>(Builders<CorporateData>.Projection
                    .Include(x => x.ComprehensiveEsgResearchState)
                    .Include(x => x.CoreEsgResearchState))
                .FirstOrDefault();
            if (company == null)
                return null;

            if (company.ComprehensiveEsgResearchState.ResearchState != ResearchState.ResearchAvailable &&
                company.CoreEsgResearchState.ResearchState != ResearchState.ResearchAvailable)
                return null;

            var esgOverallPerformanceSummary = new EsgOverallPerformanceSummary
            {
                Id = result.Id,
                CompanyId = result.Id,
                UniverseIds = result.UniverseIds
            };
            esgOverallPerformanceSummary.Overall = this.ConvertToEsgScoreSummary(result, x => x.OverallPerformance);
            esgOverallPerformanceSummary.Environment = this.ConvertToEsgScoreSummary(result, x => x.EnvironmentPerformance);
            esgOverallPerformanceSummary.Social = this.ConvertToEsgScoreSummary(result, x => x.SocialPerformance);
            esgOverallPerformanceSummary.Governance = this.ConvertToEsgScoreSummary(result, x => x.GovernancePerformance);

            esgOverallPerformanceSummary.Percentile = new EsgPercentileSummary
            {
                Percentile = Convert.ToInt32(Math.Round(result.OverallPerformance.Percentile, 0, MidpointRounding.AwayFromZero)),
                EsgPosition = result.OverallPerformance.Assessment,
                IsInReferenceUniverse = result.IsInReferenceUniverse
            };
            esgOverallPerformanceSummary.Relative = new EsgRelativePositionSummary
            {
                ComparativeGroupRank = result.OverallPerformance.Rank.GetValueOrDefault(),
                ComparativeGroupSize = result.OverallPerformance.GroupCount,
                EsgPosition = result.OverallPerformance.Assessment,
                IsInReferenceUniverse = result.IsInReferenceUniverse
            };

            //get last update
            var companyProfile = _rpCompanyProfileRepositoryCollection.Find(x => x.Id == companyId).FirstOrDefault();
            if (companyProfile != null)
            {
               if(company.ComprehensiveEsgResearchState.ResearchState == ResearchState.ResearchAvailable) esgOverallPerformanceSummary.LastUpdate = companyProfile.LastFullReviewDate;
               else if (company.CoreEsgResearchState.ResearchState == ResearchState.ResearchAvailable) esgOverallPerformanceSummary.LastUpdate = companyProfile.LastUpdate;

            } 

            return esgOverallPerformanceSummary;
        }

        public EsgRelativePosition GetEsgRelativePosition(Guid companyId, Guid esgProfileId)
        {
            var result = _esgComputationResultRepository.ReadAllWhere(x => x.Id == companyId).FirstOrDefault();

            if (result == null) return null;

            var esgRelativePosition = new EsgRelativePosition
            {
                Id = result.Id,
                CompanyId = result.Id,
                UniverseIds = result.UniverseIds,
                PositionSummary = new EsgRelativePositionSummary
                {
                    ComparativeGroupRank = result.OverallPerformance.Rank.GetValueOrDefault(),
                    ComparativeGroupSize = result.OverallPerformance.GroupCount,
                    EsgPosition = result.OverallPerformance.Assessment,
                    IsInReferenceUniverse = result.IsInReferenceUniverse
                },
                MarketCapRangeFrom = result.RelativePosition.MarketCapRangeFrom,
                MarketCapRangeTo = result.RelativePosition.MarketCapRangeTo,
                TopList = new EsgCorporateScore[0],
                PeersList = new EsgCorporateScore[0]
            };

            if (result.RelativePosition.TopList != null)
                esgRelativePosition.TopList = result.RelativePosition.TopList.Select(cs => new EsgCorporateScore
                {
                    CompanyId = cs.CompanyId,
                    CompanyName = cs.CompanyName,
                    Score = cs.Score
                }).ToArray();

            if (result.RelativePosition.PeersList != null)
                esgRelativePosition.PeersList = result.RelativePosition.PeersList.Select(cs => new EsgCorporateScore
                {
                    CompanyId = cs.CompanyId,
                    CompanyName = cs.CompanyName,
                    Score = cs.Score
                }).ToArray();

            return esgRelativePosition;
        }

        public IEnumerable<ESGRpIndicator> GetIndicatorsByCompanyThemeAndCluster(Guid companyGuid, Guid esgProfileId, string theme,
            string clusterName, IEnumerable<int> universeIds, string indicatorNumber, int? skip = null, int? take = null)
        {
            if (string.IsNullOrEmpty(clusterName)) //for focused rating
                return GetIndicatorsByCompanyTheme(companyGuid, esgProfileId, theme, universeIds, indicatorNumber);

            var resultList = new List<ESGRpIndicator>();

            var templates = _templatesRepository.ReadAllWhere(
                    x => x.ThemeName.Equals(theme) && x.IndicatorCategory.Equals(clusterName));

            var searchedTheme = templates.FirstOrDefault();

            if (!templates.Any(t => t.IsControversyIndicator))
            {
                if (searchedTheme != null)
                {
                    IList<RPIndicatorExtended> standardIndicators;
                    do
                    {
                        standardIndicators = _standardIndicatorsRepository.ReadAllWhere(companyGuid,
                                                                                        searchedTheme.ThemeId, clusterName, skip, take,
                                                                                        Ordering<RPIndicatorExtended>.Ascending(x => x.Template.SortKey));
                        resultList.AddRange(standardIndicators.Select(ESGRpIndicator.FromDatabaseObject));
                        skip += take;//todo Adrian bug if skip and take are null <- this is a superficial assertion; you can compose 2 nullable ints using += operator
                    } while (!string.IsNullOrWhiteSpace(indicatorNumber) && standardIndicators.Count == take && standardIndicators.All(i => i.Number != indicatorNumber));
                }
            }
            else
            {
                if (searchedTheme != null)
                {
                    IList<RPControversyIndicator> controversyIndicators;
                    do
                    {
                        controversyIndicators = _controversyIndicatorRepository.ReadAllWhere(companyGuid,
                                                                                                searchedTheme.ThemeId, clusterName, skip, take,
                                                                                                Ordering<RPControversyIndicator>.Ascending(x => x.Template.SortKey));

                        resultList.AddRange(controversyIndicators.Select(ESGRpIndicator.FromDatabaseObject));
                        skip += take;
                    } while (!string.IsNullOrWhiteSpace(indicatorNumber) && controversyIndicators.Count == take && controversyIndicators.All(i => i.Template.Number != indicatorNumber));
                }
            }

            if (resultList.IsNullOrEmpty())
            {
                throw new ESGReasearchNotFoundException();
            }

            var computationIndicators = _computationRepository.ReadWhere(x => x.Id == companyGuid, null,
                null, null, x => x.Indicators).FirstOrDefault();
            if (computationIndicators == null)
            {
                throw new ESGReasearchNotFoundException();
            }

            List<ESGRpIndicator> result = new List<ESGRpIndicator>();
            foreach (var indicator in resultList)
            {
                var computedIndicator = computationIndicators.Indicators.FirstOrDefault(x => x.Code.Equals(indicator.Code, StringComparison.InvariantCultureIgnoreCase));
                if (computedIndicator == null)
                    continue;
                indicator.Weight = computedIndicator.Weight;
                indicator.WeightedScore = computedIndicator.WeightedScore;
                indicator.RawScore = computedIndicator.RawScore;
                result.Add(indicator);
            }

            if (!universeIds.IsNullOrEmpty())
            {
                result = result.Where(x => universeIds.Intersect(x.UniverseIds).Any()).ToList();
                if (result.IsNullOrEmpty())
                {
                    throw new ESGReasearchNotInUniversesException();
                }
            }
            return result;
        }

        public IEnumerable<ESGRpIndicator> GetIndicatorsByCompanyTheme(Guid companyGuid, Guid esgProfileId, string theme,
            IEnumerable<int> universeIds, string indicatorNumber)
        {
            var resultList = new List<ESGRpIndicator>();

            var templates = _templatesRepository.ReadAllWhere(x => x.ThemeName.Equals(theme));

            var searchedTheme = templates.FirstOrDefault();

            if (searchedTheme != null)
            {
                IList<RPIndicatorExtended> standardIndicators;
                standardIndicators = _standardIndicatorsRepository.ReadAllWhere(companyGuid,
                                                                                    searchedTheme.ThemeId, string.Empty, null, null,
                                                                                    Ordering<RPIndicatorExtended>.Ascending(x => x.Template.SortKey)).
                                                                                    Where(x => !x.Template.IsControversyIndicator).ToList();
                resultList.AddRange(standardIndicators.Select(ESGRpIndicator.FromDatabaseObject));

                IList<RPControversyIndicator> controversyIndicators;
                controversyIndicators = _controversyIndicatorRepository.ReadAllWhere(companyGuid,
                                                                                            searchedTheme.ThemeId, string.Empty, null, null,
                                                                                            Ordering<RPControversyIndicator>.Ascending(x => x.Template.SortKey));
                resultList.AddRange(controversyIndicators.Select(ESGRpIndicator.FromDatabaseObject));
            }

            if (resultList.IsNullOrEmpty())
            {
                throw new ESGReasearchNotFoundException();
            }

            var computationIndicators = _computationRepository.ReadWhere(x => x.Id == companyGuid, null,
                null, null, x => x.Indicators).FirstOrDefault();
            if (computationIndicators == null)
            {
                throw new ESGReasearchNotFoundException();
            }

            List<ESGRpIndicator> result = new List<ESGRpIndicator>();
            foreach (var indicator in resultList)
            {
                var computedIndicator = computationIndicators.Indicators.FirstOrDefault(x => x.Code.Equals(indicator.Code, StringComparison.InvariantCultureIgnoreCase));
                if (computedIndicator == null)
                    continue;
                indicator.Weight = computedIndicator.Weight;
                indicator.WeightedScore = computedIndicator.WeightedScore;
                indicator.RawScore = computedIndicator.RawScore;
                result.Add(indicator);
            }

            if (!universeIds.IsNullOrEmpty())
            {
                result = result.Where(x => universeIds.Intersect(x.UniverseIds).Any()).ToList();
                if (result.IsNullOrEmpty())
                {
                    throw new ESGReasearchNotInUniversesException();
                }
            }

            return result;
        }

        public IEnumerable<Tuple<int, string>> GetIndicatorsClusters(string themeName)
        {
            var rpIndicatorsRepository = _templatesRepository as IRPIndicatorsRepository<IndicatorTemplate>;

            if (rpIndicatorsRepository != null)
            {
                var items = rpIndicatorsRepository.GetDistinctByFields<Cluster>(x => x.ThemeName == themeName,
                   x => x.IndicatorCategory, x => x.IndicatorCategorySortKey)
                    .OrderBy(x => x.IndicatorCategorySortKey);
                return
                    items.Select(
                        item => new Tuple<int, string>(item.IndicatorCategorySortKey, item.IndicatorCategory));
            }
            return Enumerable.Empty<Tuple<int, string>>();
        }

        /// <exception cref="ESGReasearchNotFoundException">Nof found. </exception>
        /// <exception cref="ESGReasearchNotInUniversesException">Not in universe. </exception>
        public ThemePerformance GetThemePerformance(Guid companyId, Guid esgProfileId, string theme, IEnumerable<int> universeIds)
        {

            var result = _esgComputationResultRepository.ReadAllWhere(x => x.Id == companyId).FirstOrDefault();

            ThemePerformance themePerformance = new ThemePerformance { ThemeName = theme, IsInReferenceUniverse = result.IsInReferenceUniverse };

            if (result == null) throw new ESGReasearchNotFoundException();

            if (theme == "Environment")
            {
                themePerformance.Performance = result.EnvironmentPerformance;
            }

            if (theme == "Social")
            {
                themePerformance.Performance = result.SocialPerformance;
            }

            if (theme == "Governance")
            {
                themePerformance.Performance = result.GovernancePerformance;
            }

            return themePerformance;
        }

        private EsgScoreSummary ConvertToEsgScoreSummary(RPCompanyESGComputationResult input, Func<RPCompanyESGComputationResult, RPPerformance> themeExpression)
        {
            var item = themeExpression(input);
            return item != null
                ? new EsgScoreSummary
                {
                    Score = Convert.ToInt32(item.Score),
                    EsgPosition = item.Assessment,
                    Weight = item.Weight?.Value
                }
                : new EsgScoreSummary();
        }


    }
}

